package spark.graphics;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.media.opengl.GLException;

import com.sun.opengl.util.texture.TextureCoords;
import com.sun.opengl.util.texture.TextureData;
import com.sun.opengl.util.texture.TextureIO;

import spark.exception.LoadingException;
import spark.exception.ObjectNotFoundException;
import spark.exception.ObjectAlreadyContainedException;
import spark.identity.IManageable;
import spark.identity.IIdentifiable;
import spark.identity.Id;
import spark.ObjectManager;

/**
 * Holds image data used by OpenGL to texturize surfaces.
 */
public class Texture implements IManageable {
	private static IIdentifiable bound = null;
	private IIdentifiable id;
	private com.sun.opengl.util.texture.Texture texture;

	private Texture(com.sun.opengl.util.texture.Texture texture, IIdentifiable id) {
		this.id = id;
		this.texture = texture;
	}

	public static Texture newTexture(BufferedImage image, boolean mipmap) {
		IIdentifiable id = new Id(image);
		Texture newTexture;
		try {
			newTexture = ObjectManager.getInstance().getObjectById(id);
		}
		catch(ObjectNotFoundException e) {
			newTexture = new Texture(TextureIO.newTexture(image, mipmap), id);
			try {
				ObjectManager.getInstance().insertObject(newTexture);
			}
			catch(ObjectAlreadyContainedException e1) {
				assert false;
			}
		}
		return newTexture;
	}

	public static Texture newTexture(File file, boolean mipmap) throws LoadingException {
		IIdentifiable id = new Id(file.getAbsolutePath());
		Texture newTexture;
		try {
			newTexture = ObjectManager.getInstance().getObjectById(id);
		}
		catch(ObjectNotFoundException objectNotFoundException) {
			try {
				newTexture = new Texture(TextureIO.newTexture(file, mipmap), id);
			}
			catch(GLException glException) {
				throw new LoadingException(glException);
			}
			catch(IOException ioException) {
				throw new LoadingException(ioException);
			}
			try {
				ObjectManager.getInstance().insertObject(newTexture);
			}
			catch(ObjectAlreadyContainedException e1) {
				assert false;
			}
		}
		return newTexture;
	}

	public static Texture newTexture(TextureData data) {
		IIdentifiable id = new Id(data);
		Texture newTexture;
		try {
			newTexture = ObjectManager.getInstance().getObjectById(id);
		}
		catch(ObjectNotFoundException e) {
			newTexture = new Texture(TextureIO.newTexture(data), id);
			try {
				ObjectManager.getInstance().insertObject(newTexture);
			}
			catch(ObjectAlreadyContainedException e1) {
				assert false;
			}
		}
		return newTexture;
	}

	public static Texture newTexture(URL url, boolean mipmap, String fileSuffix) throws LoadingException {
		IIdentifiable id = new Id(url.toString());
		Texture newTexture;
		try {
			newTexture = ObjectManager.getInstance().getObjectById(id);
		}
		catch(ObjectNotFoundException e) {
			try {
				newTexture = new Texture(TextureIO.newTexture(url, mipmap, fileSuffix), id);
			}
			catch(GLException glException) {
				throw new LoadingException(glException);
			}
			catch(IOException ioException) {
				throw new LoadingException(ioException);
			}
			try {
				ObjectManager.getInstance().insertObject(newTexture);
			}
			catch(ObjectAlreadyContainedException e1) {
				assert false;
			}
		}
		return newTexture;
	}

	@Override
	public IIdentifiable getId() {
		return id;
	}

	@Override
	public void release() {
		try {
			ObjectManager.getInstance().releaseObject(this);
		}
		catch(ObjectNotFoundException objectNotFoundException) {
			throw new RuntimeException("Texture was not conatained by object manager");
		}
	}

	@Override
	public void dispose() {
		try {
			texture.dispose();
		}
		catch(GLException ignore) {
			//No OpenGL context current on this thread
		}
	}

	public void bind() throws GLException {
		if(id.equals(bound)) {
			return;
		}
		texture.bind();
		bound = id;
	}

	public void disable() throws GLException {
		texture.disable();
	}

	public void enable() throws GLException {
		texture.enable();
	}

	public float getAspectRatio() {
		return texture.getAspectRatio();
	}

	public int getEstimatedMemorySize() {
		return texture.getEstimatedMemorySize();
	}

	public int getHeight() {
		return texture.getHeight();
	}

	public int getImageHeight() {
		return texture.getImageHeight();
	}

	public TextureCoords getImageTexCoords() {
		return texture.getImageTexCoords();
	}

	public int getImageWidth() {
		return texture.getImageWidth();
	}

	public boolean getMustFlipVertically() {
		return texture.getMustFlipVertically();
	}

	public TextureCoords getSubImageTexCoords(int arg0, int arg1, int arg2, int arg3) {
		return texture.getSubImageTexCoords(arg0, arg1, arg2, arg3);
	}

	public int getTarget() {
		return texture.getTarget();
	}

	public int getTextureObject() {
		return texture.getTextureObject();
	}

	public int getWidth() {
		return texture.getWidth();
	}

	public boolean isUsingAutoMipmapGeneration() {
		return texture.isUsingAutoMipmapGeneration();
	}

	public void setTexParameterf(int arg0, float arg1) {
		texture.setTexParameterf(arg0, arg1);
	}

	public void setTexParameterfv(int arg0, float[] arg1, int arg2) {
		texture.setTexParameterfv(arg0, arg1, arg2);
	}

	public void setTexParameterfv(int arg0, FloatBuffer arg1) {
		texture.setTexParameterfv(arg0, arg1);
	}

	public void setTexParameteri(int arg0, int arg1) {
		texture.setTexParameteri(arg0, arg1);
	}

	public void setTexParameteriv(int arg0, int[] arg1, int arg2) {
		texture.setTexParameteriv(arg0, arg1, arg2);
	}

	public void setTexParameteriv(int arg0, IntBuffer arg1) {
		texture.setTexParameteriv(arg0, arg1);
	}
}
